/*
 *  Scene.cpp
 *  rayTracer
 *
 *  Created by Zachry Thayer on 10/6/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */

#include "Scene.h"
#include <stdio.h>
#include <algorithm>
#include <omp.h>

int Scene::addObject(Object *o){
	objects.push_back(o);
	return objects.size();
}

void Scene::removeObject(int o){
	objects.erase(objects.begin()+o);
}

int Scene::addLight(Light *l){
	lights.push_back(l);
	return lights.size();
}

void Scene::removeLight(int l){
	lights.erase(lights.begin()+l);
}

void Scene::putColorBuf(int x, int y, unsigned int color){
	if(x >= 0 && x < w && y >= 0 && y < h)
		buffer[x + (y * w)] = color;
}

unsigned int Scene::getColorBuf(int x, int y){
	if(x >= 0 && x < w && y >= 0 && y < h)
		return buffer[x + (y * w)];
	
	return 0xDEADBEEF;
}

void Scene::draw(){	
	#pragma omp parallel for schedule(dynamic)
	for(int y = 0; y < h; y ++)
		for(int x = 0; x < w; x ++){
			Vec3 castDir(x - (w*0.5) ,y - (h * 0.5f), dir.z);
			castDir.normalize();
			Ray cast(pos,castDir);
			unsigned int final = trace(cast);
			putColorBuf(x, y, final);
		}
	
}

Intersect Scene::closestCollision(Ray &ray){
	float dist = 20000.f;
	Intersect res, ret;
	int obj = -1;
	for (int i = 0; i < objectCount(); i ++){// find nearest object
		res = objects[i]->intersect(ray);
		if(res.hit == true){//if hit
			if (res.dist < dist) {// if closer than last
				ret = res;//store 
				dist = res.dist;
				obj = i;
			}
		}
	}
	ret.obj = obj;
	return ret;
}


unsigned int Scene::trace(Ray &ray){
	
	float coef = 1.0f;
	int reflects = 0;
	float red = 0.f,green = 0.f,blue = 0.f;
	
	for (coef = 1.0f, reflects = 0; coef > 0.f , reflects <= reflectDepth; reflects ++) {
		//ret = 0x0;
		
		Intersect intersect = closestCollision(ray);
		
		if(intersect.hit == true){//if we actually collide with something
			for (int i = 0; i < lights.size(); i ++) {//for each light
				
				//Ray towards light
				Vec3 shadowDir =lights[i]->getPosition() - intersect.point;
				shadowDir.normalize();
				Ray shadowRay(intersect.point, shadowDir);
				
				Intersect lightIntersect;
				
				for (int j = 0; j < objectCount(); j ++){// find blocking object (shadow)
					lightIntersect = objects[j]->intersect(shadowRay);
					if(lightIntersect.hit)
						break;
				}
				
				if(!lightIntersect.hit){//light, no blocking object
					Material lmat = lights[i]->getMaterial();
					float diffuse = shadowDir.dot(intersect.normal) * coef * lights[i]->getScale().x;
					if (diffuse < 0) {
						diffuse = 0;
					}
					red += intersect.mat.r * lmat.r * diffuse;
					green += intersect.mat.g *  lmat.g * diffuse;
					blue += intersect.mat.b * lmat.b * diffuse;
				}
								
			}
			
			coef *= intersect.mat.reflect;
			ray = Ray(intersect.point, intersect.reflect);
			
			if(intersect.mat.reflect == 0.f)
				break;
		}else { // no object get background
			//red = green = blue = ray.dir.y;
		}
		
	}
	
	return COLOR_RGBAF(red,green,blue,1.0f);;	
}


unsigned int Scene::getBackground(Ray &ray){
	char r = (char)255-(255 * (1+ray.dir.y)/2);
	//Todo: calculate color based on ray height
	//return COLOR_RGBA(0,0,0,255);
	return COLOR_RGBA(r, r, r, 255);
	
}

void Scene::saveImageTarga(const char* filename, unsigned int* data, int width, int height, int lineSize, int saveAlpha)
{
	FILE* fp;
	int x, y;
	tga_header_t head;
	
	if ((fp = fopen(filename, "wb")) == NULL) return;
	
	// Put the header.
	head.id_length=0;
	fwrite(&head.id_length,1,1,fp);
	head.colormap_type=0;
	fwrite(&head.colormap_type,1,1,fp);
	head.image_type=2;
	fwrite(&head.image_type,1,1,fp);
	head.cm_first_entry=0;
	fwrite(&head.cm_first_entry,2,1,fp);
	head.cm_length=0;
	fwrite(&head.cm_length,2,1,fp);
	head.cm_size=0;
	fwrite(&head.cm_size,1,1,fp);
	head.x_origin=0;
	fwrite(&head.x_origin,2,1,fp);
	head.y_origin=0;
	fwrite(&head.y_origin,2,1,fp);
	head.width=width;
	fwrite(&head.width,2,1,fp);
	head.height=height;
	fwrite(&head.height,2,1,fp);
	head.pixel_depth=24;
	fwrite(&head.pixel_depth,1,1,fp);
	head.image_descriptor=0x00;
	fwrite(&head.image_descriptor,1,1,fp);
	
	for(y=height-1;y>=0;y--) {
		for(x=0;x<width;x++) {
			fwrite((char *)(data+y*lineSize+x)+2,1,1,fp);
			fwrite((char *)(data+y*lineSize+x)+1,1,1,fp);
			fwrite((char *)(data+y*lineSize+x)+0,1,1,fp);
		}
	}
	
	fclose(fp);
}

void Scene::saveRender(char *file){
	saveImageTarga(file, buffer, w, h, w, 0);
}

Vec3 Scene::getPosition(){
	return pos;
}

void Scene::setPosition(Vec3 &position){
	pos = position;
}

void Scene::translate(Vec3 &trans){
	pos = pos + trans;
}


int Scene::getWidth(){return w;}
int Scene::getHieght(){return h;}
void *Scene::getBuffer(){return buffer;}
int Scene::objectCount(){return objects.size();}
